package com.king.armips;

import static com.king.armips.ArgumentsParseException.*;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class ArmipsArguments {
    // common
    public int symFileVersion = 0;
    public boolean errorOnWarning;
    public boolean silent = !BuildConfig.DEBUG;
    public boolean showStats;
    public List<EquationDefinition> equList = new ArrayList<>();
    public List<LabelDefinition> labels = new ArrayList<>();

    // file mode
    public String inputFileName;
    public String tempFileName;
    public String symFileName;
    public boolean useAbsoluteFileNames = true;
    public String rootDir;

    public static class EquationDefinition {
        public String name;
        public String value;

        public EquationDefinition(String name, String replacement){
            this(name, replacement, false);
        }

        public EquationDefinition(String name, String replacement,boolean str) {
            this.name = name;
            if (str) {
                this.value = "\"" + replacement + "\"";
            }
            else {
                this.value = replacement;
            }
        }

    }

    public static class LabelDefinition {
        String name;
        long value;

        public LabelDefinition(String name, long value) {
            this.name = name;
            this.value = value;
        }
    }

    public static ArmipsArguments parseArguments(String string) throws ArgumentsParseException {
        if (string == null)
            throw new ArgumentsParseException(ERROR_NULL_ARGUMENTS,"Arguments string is null");
        String trim = string.trim();
        ArmipsArguments settings = new ArmipsArguments();
        if (trim.isEmpty())
            return settings;
        String[] arguments = trim.split("\\s+");
        int argPos = 0;
        boolean readFlags = true;

        LinkedHashMap<String,EquationDefinition> equMap = new LinkedHashMap<>();
        LinkedHashMap<String,LabelDefinition> labelMap = new LinkedHashMap<>();

        int errorCode = 0;
        String errorMessage = null;
        String errorArg = null;
        while (argPos < arguments.length){
            if (errorMessage != null)
                break;

            String arg = arguments[argPos];

            if (readFlags && arg.charAt(0) == '-'){
                switch (arg){
                    case "--":
                        readFlags = false;
                        break;
                    case "-temp":
                        if (argPos+1 >= arguments.length) {
                            errorCode = ERROR_MISSING_ARGUMENT_TEMP;
                            errorMessage = "Missing argument for \"-temp\"";
                            break;
                        }
                        argPos++;
                        settings.tempFileName = arguments[argPos];
                        break;
                    case "-sym":
                        argPos++;
                        settings.symFileName = arguments[argPos];
                        settings.symFileVersion = 1;
                        break;
                    case "-sym2":
                        argPos++;
                        settings.symFileName = arguments[argPos];
                        settings.symFileVersion = 2;
                        break;
                    case "-erroronwarning":
                        settings.errorOnWarning = true;
                        break;
                    case "-stat":
                        settings.showStats = true;
                        break;
                    case "-equ": {
                        if (argPos + 2 >= arguments.length) {
                            errorCode = ERROR_MISSING_ARGUMENTS_EQU;
                            errorMessage = "Missing arguments for \"-equ\"";
                            break;
                        }
                        argPos++;
                        String name = arguments[argPos];
                        if (!isValidSymbolName(name)){
                            errorCode = ERROR_INVALID_EQUATION_NAME;
                            errorArg = name;
                            errorMessage = "Invalid equation name \"" + name + "\"";
                            break;
                        }
                        argPos++;
                        String value = arguments[argPos];
                        equMap.put(name,new EquationDefinition(name, value));
                    }break;
                    case "-strequ":{
                        if (argPos + 2 >= arguments.length) {
                            errorCode = ERROR_MISSING_ARGUMENTS_EQU;
                            errorMessage = "Missing arguments for \"-equ\"";
                            break;
                        }
                        argPos++;
                        String name = arguments[argPos];
                        if (!isValidSymbolName(name)){
                            errorCode = ERROR_INVALID_EQUATION_NAME;
                            errorArg = name;
                            errorMessage = "Invalid equation name \"" + name + "\"";
                            break;
                        }
                        argPos++;
                        String value = "\"" + arguments[argPos] + "\"";
                        equMap.put(name,new EquationDefinition(name, value));
                    }break;
                    case "-time":
                        errorCode = ERROR_DEPRECATED_TIME;
                        errorMessage = "\"-time\" flag is deprecated";
                        break;
                    case "-root":
                        errorCode = ERROR_ROOT_NOT_SUPPORTED;
                        errorMessage = "Root directory is not supported";
                        break;
                    case "-definelabel":{
                        if (argPos + 2 >= arguments.length) {
                            errorCode = ERROR_MISSING_ARGUMENTS_DEFINELABEL;
                            errorMessage = "Missing arguments for \"-definelabel\"";
                            break;
                        }
                        argPos++;
                        String name = arguments[argPos];
                        if (!isValidSymbolName(name)){
                            errorCode = ERROR_INVALID_LABEL_NAME;
                            errorArg = name;
                            errorMessage = "Invalid label name \"" + name + "\"";
                            break;
                        }
                        argPos++;
                        String value = arguments[argPos];
                        AtomicInteger valueInt = new AtomicInteger();
                        if (!stringToInt(value,valueInt)){
                            errorCode = ERROR_INVALID_LABEL_VALUE;
                            errorArg = value;
                            errorMessage = "Invalid label value \"" + value + "\"";
                            break;
                        }
                        labelMap.put(name,new LabelDefinition(name,valueInt.get()));
                    }break;
                    default:
                        errorCode = ERROR_INVALID_ARGUMENT;
                        errorArg = arg;
                        errorMessage = "Invalid argument \"" + arg + "\"";
                        break;
                }
            }

            argPos++;
        }

        if (errorMessage != null){
            throw new ArgumentsParseException(errorCode,errorMessage).setArgs(errorArg);
        }

        return settings;
    }

    private static boolean isValidSymbolName(String string){
        if (string == null || string.isEmpty())
            return false;
        if (string.equals("@") || string.equals("@@"))
            return false;

        int size = string.length();
        int start = 0;

        if (string.charAt(0) == '@') {
            start++;
            if (size > 1 && string.charAt(1) == '@')
                start++;
        }

        if (string.charAt(start) >= '0' && string.charAt(start) <= '9')
            return false;

        for (int i = start; i < size; i++) {
            if (!Character.isLetterOrDigit(string.charAt(i)))
                return false;
        }

        return true;
    }

    private static boolean stringToInt(String string, AtomicInteger value){
        if (string == null || string.isEmpty())
            return false;
        String line = string.toLowerCase();

        if (line.startsWith("0x")){
            if (!line.matches("^0x[0-9a-f]+$"))
                return false;
            value.set(Integer.parseInt(line.substring(2),16));
            return true;
        }

        if (line.startsWith("0o")){
            if (!line.matches("^0o[0-7]+$"))
                return false;
            value.set(Integer.parseInt(line.substring(2),8));
            return true;
        }

        if (line.startsWith("0b")){
            if (!line.matches("^0b[01]+$"))
                return false;
            value.set(Integer.parseInt(line.substring(2),2));
            return true;
        }

        if (line.endsWith("h")){
            if (!line.matches("^[0-9a-f]+h$"))
                return false;
            value.set(Integer.parseInt(line.substring(0,line.length()-1),16));
            return true;
        }

        if (line.endsWith("o")){
            if (!line.matches("^[0-7]+o$"))
                return false;
            value.set(Integer.parseInt(line.substring(0,line.length()-1),8));
            return true;
        }

        if (line.endsWith("b")){
            if (!line.matches("^[01]+b$"))
                return false;
            value.set(Integer.parseInt(line.substring(0,line.length()-1),2));
            return true;
        }

        if (!line.matches("^[0-9]+$"))
            return false;

        value.set(Integer.parseInt(line));

        return true;
    }

}
